﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Windows.Forms;

using Rsdn.Janus.ObjectModel;
using Rsdn.SmartApp;
using Rsdn.TreeGrid;

namespace Rsdn.Janus
{
    /// <summary>
    /// Обработчик команд Outbox'а.
    /// </summary>
    [CommandTarget]
    internal sealed class OutboxCommandTarget : CommandTarget
    {
        public OutboxCommandTarget(IServiceProvider serviceProvider)
            : base(serviceProvider) { }

        [CommandExecutor("Janus.Outbox.EditItem")]
        public void ExecuteEditItem(ICommandContext context)
        {
            IActiveOutboxItemService activeOutboxItemService = context.GetRequiredService<IActiveOutboxItemService>();

            IOutboxManagerBase currentMessageManager = activeOutboxItemService.GetCurrentOutboxManager();
            IOutboxManager outboxManager = currentMessageManager as IOutboxManager;

            ICollection<ITreeNode> selectedNodes = currentMessageManager.OutboxForm.SelectedNodes;

            Dictionary<ITreeNode, IOutboxItemEditor> editors = CreateEditor(context, selectedNodes, true);
            if (outboxManager == null || outboxManager.IsAllowedEditMessages)
            {
                foreach (KeyValuePair<ITreeNode, IOutboxItemEditor> pair in editors)
                {
                    IOutboxItemEditor itemEditor = pair.Value;

                    ITreeNode treeNode = pair.Key;
                    itemEditor.Edit(treeNode);
                }
            }
        }

        [CommandStatusGetter("Janus.Outbox.EditItem")]
        public CommandStatus QueryEditItemStatus(ICommandContext context)
        {
            IActiveOutboxItemService activeOutboxItemService = context.GetRequiredService<IActiveOutboxItemService>();
            IOutboxManager outboxManager = activeOutboxItemService.GetCurrentOutboxManager() as IOutboxManager;

            CommandStatus commandStatus = QueryOutboxItemCommandStatus(context);
            return commandStatus.UnavailableIfNot(
                delegate
                    {
                        if (outboxManager == null || outboxManager.IsAllowedEditMessages)
                        {
                            Dictionary<ITreeNode, IOutboxItemEditor> editors = CreateEditor(
                                context, activeOutboxItemService.ActiveOutboxItems, true);

                            return editors.Any();
                        }

                        return false;
                    });
        }

        [CommandExecutor("Janus.Outbox.DeleteItem")]
        public void ExecuteDeleteItem(ICommandContext context)
        {
            if (MessageBox.Show(
                    SR.Outbox.RemoveMessageConfirm,
                    ApplicationInfo.ApplicationName,
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Warning) != DialogResult.Yes)
            {
                return;
            }

            IActiveOutboxItemService activeOutboxItemService = context.GetRequiredService<IActiveOutboxItemService>();

            IOutboxManagerBase currentOutboxManager = activeOutboxItemService.GetCurrentOutboxManager();
            ICollection<ITreeNode> selectedNodes = currentOutboxManager.OutboxForm.SelectedNodes;
            Dictionary<ITreeNode, IOutboxItemEditor> outboxItemEditors = CreateEditor(context, selectedNodes, false);

            outboxItemEditors.ForEach(kvp => kvp.Value.Delete(kvp.Key));
        }

        [CommandStatusGetter("Janus.Outbox.DeleteItem")]
        public CommandStatus QueryDeleteItemStatus(ICommandContext context)
        {
            IActiveOutboxItemService activeOutboxItemService = context.GetRequiredService<IActiveOutboxItemService>();

            return QueryOutboxItemCommandStatus(context).UnavailableIfNot(
                () => CreateEditor(context, activeOutboxItemService.ActiveOutboxItems, false).Any());
        }

        private static CommandStatus QueryOutboxItemCommandStatus(IServiceProvider provider)
        {
            IActiveOutboxItemService activeOutboxItemSvc = provider.GetService<IActiveOutboxItemService>();
            if (activeOutboxItemSvc == null)
            {
                return CommandStatus.Unavailable;
            }

            bool isAnyItems = activeOutboxItemSvc.ActiveOutboxItems.Any();
            return isAnyItems
                       ? CommandStatus.Normal
                       : CommandStatus.Unavailable;
        }

        [CommandStatusSubscriber("Janus.Outbox.EditItem")]
        [CommandStatusSubscriber("Janus.Outbox.DeleteItem")]
        public IDisposable SubscribeOutboxFeatureCommandStatusChanged(
            IServiceProvider provider, Action handler)
        {
            IActiveOutboxItemService outboxSvc = provider.GetService<IActiveOutboxItemService>();
            if (outboxSvc != null)
            {
                EventHandler statusUpdater = (sender, e) => handler();
                Features.AfterFeatureActivateHandler statusUpdater2 = (oldFeature, newFeature) => handler();
                outboxSvc.ActiveOutboxItemsChanged += statusUpdater;
                Features.Instance.OnAfterFeatureActivate += statusUpdater2;

                return Disposable.Create(
                    () =>
                    {
                        outboxSvc.ActiveOutboxItemsChanged -= statusUpdater;
                        Features.Instance.OnAfterFeatureActivate -= statusUpdater2;
                    });
            }
            return Disposable.Empty;
        }

 private static Type FindEditor(object item)
        {
            OutboxItemEditorAttribute editorAttribute =
                Attribute.GetCustomAttribute(item.GetType(), typeof(OutboxItemEditorAttribute)) as OutboxItemEditorAttribute;

            return editorAttribute != null ? editorAttribute.EditorType : null;
        }

        private readonly Dictionary<Type, IOutboxItemEditor> _editorHash = new Dictionary<Type, IOutboxItemEditor>();

        /// <summary>
        /// Creates the editor.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private IOutboxItemEditor CreateEditor(IServiceProvider provider, object item)
        {
            if (item == null)
            {
                return null;
            }

            if (_editorHash.ContainsKey(item.GetType()))
            {
                return _editorHash[item.GetType()];
            }

            Type editor = FindEditor(item);

            if (editor == null)
            {
                return null;
            }

            IOutboxItemEditor e = (IOutboxItemEditor)editor.CreateInstance(provider);

            _editorHash.Add(item.GetType(), e);
            return e;
        }

        /// <summary>
        /// Creates the editor.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="nodes">The nodes.</param>
        /// <param name="edit">if set to <c>true</c> [edit].</param>
        /// <returns></returns>
        private Dictionary<ITreeNode, IOutboxItemEditor> CreateEditor(
            IServiceProvider provider,
            ICollection<ITreeNode> nodes,
            bool edit)
        {
            bool allowAdd = true;
            Dictionary<ITreeNode, IOutboxItemEditor> editors = new Dictionary<ITreeNode, IOutboxItemEditor>(nodes.Count);

            foreach (ITreeNode node in nodes)
            {
                IOutboxItemEditor editor = CreateEditor(provider, node);

                allowAdd &= editor != null
                            && (edit ? editor.AllowEdit(node) : editor.AllowDelete(node));

                if (!allowAdd)
                {
                    break;
                }

                editors.Add(node, editor);
            }

            if (!allowAdd)
            {
                editors.Clear();
            }

            return editors;
        }
    }
}